package com.ldr.custom.spring.v3.webmvc;

import com.ldr.custom.spring.annotation.GPController;
import com.ldr.custom.spring.annotation.GPRequestMapping;
import com.ldr.custom.spring.annotation.GPRequestParam;
import com.ldr.custom.spring.v3.aop.utils.GPAopProxyUtils;
import com.ldr.custom.spring.v3.context.GPApplicationContext;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Servlet只是作为一个MVC的启动入口
 */
public class GPDispatcherServlet extends HttpServlet {
    //固定常量为何加final，因为值是不可变，如果不加final，有可能使用反射的强制访问，修改值，影响代码健壮性
    private final String LOCATION = "contextConfigLocation";
    //spring最核心的设计，也是最经典的
    //它牛逼到直接干掉Struts，Webwork等MVC框架
    private List<GPHandlerMapping> handlerMappings = new ArrayList<>();

    private Map<GPHandlerMapping, GPHandlerAdapter> handlerAdapters = new HashMap<>();

    private List<GPViewResolver> viewResolvers = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {

            doDispatch(req, resp);
        } catch (
                Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Details:\r\n"
                    + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "\r\n"));

        }


    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        //根据用户请求的url来获得一Handler
        GPHandlerMapping handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("<font size='25' color='red'>404 Not Found\r\n @GuPaoMVC</font>");
            return;
        }
        //解决乱码问题
        decode(resp);

        GPHandlerAdapter ha = getHandlerAdapter(handler);
        //这一步只是调用方法，得到返回值(request请求参数（实参），对方法的形参赋值，并且返回ModelAndView)
        GPModelAndView mv = ha.handle(req, resp, handler);

        //这一步才是真正的输出
        processDispatchResult(resp, mv);


    }

    private void processDispatchResult(HttpServletResponse resp, GPModelAndView mv) throws IOException {

        //调用viewResolver的resolverView方法
        if (mv == null) {
            return;
        }
        if (this.viewResolvers.isEmpty()) {
            return;
        }

        for (GPViewResolver viewResolver : this.viewResolvers) {
            if (!viewResolver.getViewName().equals(mv.getViewName())) {
                continue;
            }
            String out = viewResolver.viewResolver(mv);
            if (out != null) {
                resp.getWriter().write(out);
                break;
            }
        }

    }

    private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handlerMapping) {

        if (this.handlerAdapters.isEmpty()) {
            return null;
        }
        return this.handlerAdapters.get(handlerMapping);
    }

    private GPHandlerMapping getHandler(HttpServletRequest req) {

        if (this.handlerMappings.isEmpty()) {
            return null;
        }

        String url = req.getRequestURI();
        for (GPHandlerMapping handlerMapping : handlerMappings) {
            Matcher matcher = handlerMapping.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handlerMapping;
        }


        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化IOC容器
        GPApplicationContext context = new GPApplicationContext(config.getInitParameter(this.LOCATION));
        initStrategies(context);

    }


    protected void initStrategies(GPApplicationContext context) {
        /**
         * 九种策略
         * 针对于每个用户请求，都会经过一些处理的策略之后，最终才能有结果输出
         * 每种策略可以自定义干预，但是最终的结果都是一致
         * GPModelAndView
         *
         * ====================这里说的就是传说中的九大组件===================
         */


        initMultipartResolver(context);//文件上传解析，如果请求类型是multipart讲通过MultipartResolver解析
        initLocaleResolver(context);//本地解析
        initThemeResolver(context);//主体解析

        //用来保存Controller中配置的RequestMapping和Method的一个对应关系
        initHandlerMappings(context);//通过HandlerMapping，将请求映射到处理器（模仿）

        //HandlerAdapters 用来动态匹配Method参数，包括类型转换，动态赋值
        initHandlerAdapters(context);//通过HandlerAdapter进行多类型的参数动态匹配（模仿）

        initHandlerExceptionResolvers(context);//异常处理器
        initRequestToViewNameTranslator(context);//直接解析请求到视图名

        //通过ViewResolvers实现动态模版的解析
        //自己解析一套模版语言
        initViewResolvers(context);//通过ViewResolver解析逻辑视图到具体视图实现（模仿）

        initFlashMapManager(context);//flash映射管理器
    }

    private void initFlashMapManager(GPApplicationContext context) {

    }


    //初始化视图解析器
    private void initViewResolvers(GPApplicationContext context) {
        //在页面敲一个http：//localhost/first.html
        //解决一个页面名字和模版文件关联的问题
        String templatePath = context.getConfig().getProperty("templateRoot");

        String filePath = this.getClass().getClassLoader().getResource(templatePath).getFile();
        initViewResolvers(filePath);
    }

    //递归查询模版（资源）文件
    private void initViewResolvers(String templatePath) {
        File templateRootDir = new File(templatePath);

        for (File file : templateRootDir.listFiles()) {
            if (file.isDirectory()) {
                initViewResolvers(templatePath + file.getName() + "/");
            }
            this.viewResolvers.add(new GPViewResolver(file.getName(), file));
        }
    }

    private void initRequestToViewNameTranslator(GPApplicationContext context) {

    }

    private void initHandlerExceptionResolvers(GPApplicationContext context) {

    }

    private void initHandlerAdapters(GPApplicationContext context) {


        //在初始化阶段，将这些参数的名字或者类型按一定的顺序保存下来，
        //因为后面用反射调用的时候，传的形参是一个数组
        //可以通过记录这些参数的位置index，挨个从数组中填值，这样的话，就和参数顺序无关了

        for (int i = 0; i < this.handlerMappings.size(); i++) {
            GPHandlerMapping handlerMapping = this.handlerMappings.get(i);

            Method method = handlerMapping.getMethod();

            Map<String, Integer> paramMapping = new HashMap<>();

            Parameter[] parameters = method.getParameters();
            for (int j = 0; j < parameters.length; j++) {
                Parameter parameter = parameters[j];

                if (parameter.isAnnotationPresent(GPRequestParam.class)) {
                    GPRequestParam requestMapping = parameter.getDeclaredAnnotation(GPRequestParam.class);
                    if (!requestMapping.value().isEmpty()) {
                        paramMapping.put(requestMapping.value().trim(), j);
                    }
                }

                if (parameter.getType() == HttpServletRequest.class
                        || parameter.getType() == HttpServletResponse.class) {

                    paramMapping.put(parameter.getType().getSimpleName(), j);

                }
            }

            this.handlerAdapters.put(handlerMapping, new GPHandlerAdapter(paramMapping));

        }


    }


    //将Controller中配置的RequestMapping和Method进行一一对应
    private void initHandlerMappings(GPApplicationContext context) {
        //首先从容器中获取所有的实例
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        try {
            for (String beanDefinitionName : beanDefinitionNames) {
                //到了mvc层，对外提供的方法只有一个getBean方法，返回对象不是BeanWrapper，怎么办
                //getBean是获取aop代理对象，造成注解拿不到情况
                Object controller = context.getBean(beanDefinitionName);

                Class<?> clazz = controller.getClass();
                if (!clazz.isAnnotationPresent(GPController.class)) {
                    continue;
                }

                String baseUrl = "";

                if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                    GPRequestMapping mapping = clazz.getAnnotation(GPRequestMapping.class);
                    baseUrl = mapping.value();
                }

                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(GPRequestMapping.class)) {
                        GPRequestMapping methodMapping = method.getDeclaredAnnotation(GPRequestMapping.class);

                        //封装成正则对象
                        String regex = ("/" + baseUrl + "/" + methodMapping.value().replaceAll("\\*", "\\.*")).replaceAll("/+", "/");

                        Pattern pattern = Pattern.compile(regex);

                        this.handlerMappings.add(new GPHandlerMapping(pattern, controller, method));
                        System.out.println("Mapping: " + regex + "," + method);

                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initThemeResolver(GPApplicationContext context) {

    }

    private void initLocaleResolver(GPApplicationContext context) {

    }

    private void initMultipartResolver(GPApplicationContext context) {

    }


    private void decode(HttpServletResponse response) {
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
    }


}
